﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Processing;
using Cloo;
using Processing.Structures;

namespace RayTracer.Kernels
{
    /// <summary>
    /// Schnittpunkt mit einem Objekt: Hierbei gilt es den Schnittpunkt und Einfallsrichtung
    /// eines Strahls mit einem spezifischen Objekt zu ermitteln. Wobei dieses Objekt
    /// verschiedene Typen haben kann.
    /// </summary>
    abstract class AKernelPrimitive : AKernel
    {
        #region Attribute
        #region GPU
        protected Buffer<Float4> gpu_rayPos;
        protected Buffer<Float4> gpu_rayDir;
        protected Buffer<float> gpu_lastDist;

        protected Buffer<Float4> gpu_objPos;
        protected Buffer<Float4> gpu_objRot;
        protected Buffer<Float16> gpu_objMat;

        protected Buffer<float> gpu_dist;
        protected Buffer<Float16> gpu_mat;
        protected Buffer<Float4> gpu_intersec;
        protected Buffer<Float4> gpu_normal;
        #endregion GPU
        #endregion Attribute

        #region Eigenschaften
        #region Dateninitialisierung
        /// <summary>
        /// Gibt und setzt die Positionen der Strahlen.
        /// </summary>
        public Buffer<Float4> RayPositions { get { return gpu_rayPos; } set { gpu_rayPos = value; } }

        /// <summary>
        /// Gibt und setzt die Richtungen der Strahlen.
        /// </summary>
        public Buffer<Float4> RayDirections { get { return gpu_rayDir; } set { gpu_rayDir = value; } }

        /// <summary>
        /// Gibt und setzt die Richtungen der Strahlen.
        /// </summary>
        public Buffer<float> LastDistances { get { return gpu_lastDist; } set { gpu_lastDist = value; } }

        /// <summary>
        /// Gibt und setzt die Objektpositionen.
        /// </summary>
        public Buffer<Float4> ObjectPositions { get { return gpu_objPos; } set { gpu_objPos = value; } }

        /// <summary>
        /// Gibt und setzt die Objektrotationen.
        /// </summary>
        public Buffer<Float4> ObjectRotations { get { return gpu_objRot; } set { gpu_objRot = value; } }

        /// <summary>
        /// Gibt und setzt die Objektmaterialien.
        /// </summary>
        public Buffer<Float16> ObjectMaterials { get { return gpu_objMat; } set { gpu_objMat = value; } }

        /// <summary>
        /// Gibt die errechneten minimalen Distanzen an.
        /// </summary>
        public Buffer<float> NewDistances
        {
            get
            {
                if (State != KernelState.Done) throw new Exception("Die Kernel wurde noch nicht berechnet.");
                return gpu_dist;
            }
        }

        /// <summary>
        /// Gibt die Objektmaterialien an den minimalen Distanzen an.
        /// </summary>
        public Buffer<Float16> Materials
        {
            get
            {
                if (State != KernelState.Done) throw new Exception("Die Kernel wurde noch nicht berechnet.");
                return gpu_mat;
            }
            set { gpu_mat = value; }
        }

        /// <summary>
        /// Gibt die errechneten Schnittpunkte an.
        /// </summary>
        public Buffer<Float4> Intersections
        {
            get
            {
                if (State != KernelState.Done) throw new Exception("Die Kernel wurde noch nicht berechnet.");
                return gpu_intersec;
            }
            set { gpu_intersec = value; }
        }

        /// <summary>
        /// Gibt die errechneten Normalen an.
        /// </summary>
        public Buffer<Float4> Normals
        {
            get
            {
                if (State != KernelState.Done) throw new Exception("Die Kernel wurde noch nicht berechnet.");
                return gpu_normal;
            }
            set { gpu_normal = value; }
        }
        #endregion Dateninitialisierung

        #region Kernelausführung
        /// <summary>
        /// Gibt die Dimension des Programms an.
        /// </summary>
        protected override long[] Dimensions { get { return new long[] { gpu_rayPos.Length }; } }

        /// <summary>
        /// Gibt an ob die Kerneldaten valid initialisiert sind.
        /// </summary>
        protected override bool IsValidInitialized
        {
            get
            {
                if (gpu_rayPos == null) throw new RayTracerException("Die Strahlenpositionen müssen angegeben werden.");
                if (gpu_rayDir == null) throw new RayTracerException("Die Strahlenrichtungen müssen angegeben werden.");
                if (gpu_objPos == null) throw new RayTracerException("Die Objektpositionen müssen angegeben werden.");
                if (gpu_objRot == null) throw new RayTracerException("Die Objektrotationen müssen angegeben werden.");
                if (gpu_objMat == null) throw new RayTracerException("Die Objektmaterialien müssen angegeben werden.");
                return
                    gpu_rayPos.Length == gpu_rayDir.Length &&
                    (gpu_lastDist == null || gpu_lastDist.Length == gpu_rayDir.Length) &&
                    gpu_objPos.Length == gpu_objRot.Length &&
                    gpu_objPos.Length == gpu_objMat.Length &&
                    gpu_rayDir.Length > 0 &&
                    (gpu_mat == null) || gpu_mat.Length == gpu_rayDir.Length &&
                    (gpu_intersec == null || gpu_intersec.Length == gpu_rayDir.Length) &&
                    (gpu_normal == null || gpu_normal.Length == gpu_rayDir.Length);
            }
        }
        #endregion Kernelausführung
        #endregion Eigenschaften

        #region Methoden
        /// <summary>
        /// Instanziiert und initialisiert die Kernel.
        /// </summary>
        /// <param name="processor">Der Prozessor.</param>
        public AKernelPrimitive(Processor processor) : base(processor) { }

        /// <summary>
        /// Führt Aktionen vor der Ausführung der Kernel - mit dem Run-Aufruf aus. (z. B. Datentransport von CPU zu GPU)
        /// </summary>
        protected override void PrepareKernelExecution()
        {
            base.PrepareKernelExecution();
            if (gpu_lastDist == null) gpu_lastDist = Processor.AllocateBuffer<float>(gpu_rayPos.Length);

            gpu_dist = Processor.AllocateBuffer<float>(gpu_rayPos.Length);
            if (gpu_mat == null) gpu_mat = Processor.AllocateBuffer<Float16>(gpu_rayPos.Length);
            if (gpu_intersec == null) gpu_intersec = Processor.AllocateBuffer<Float4>(gpu_rayPos.Length);
            if (gpu_normal == null) gpu_normal = Processor.AllocateBuffer<Float4>(gpu_rayPos.Length);
        }

        /// <summary>
        /// Gibt das Objekt für den GarbageCollector frei.
        /// </summary>
        public override void Dispose()
        {
            base.Dispose();
            gpu_lastDist.Dispose();
        }
        #endregion Methoden
    }
}
